﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

using FancyBattles;
using FancyBattles.GameEntities;
using FancyBattles.GameScreen.StateMachine;

namespace FancyBattles.GameMind
{
    class Overmind
    {
        private const int HEALTH_LIMIT_SUPERIOR = 60;
        private const int HEALTH_LIMIT_INFERIOR = 30;

        private const String HEALTH_LOW = "low";
        private const String HEALTH_MEDIUM = "medium";
        private const String HEALTH_HIGH = "high";

        private const float CORRECTION_FACTOR = 1.1f;
        private const int POSITION_CORRECTION = 20;

        #region Interface

        /// <summary>
        /// Entry point for AI actions for BouncingBitches.
        /// </summary>
        /// <param name="bitch">BouncingBitch to be moved.</param>
        /// <param name="bitches">List of all playing BouncingBitches.</param>
        /// <returns>Action description, or zero if BouncingBitch is not defined.</returns>
        public Vector2 Handle(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            Vector2 movement = Vector2.Zero;
            if (bitch.GetType() == typeof(Archer))
            {
                movement = MoveArcher(bitch, bitches);
            }
            if (bitch.GetType() == typeof(Gladiator))
            {
                movement = MoveGladiator(bitch, bitches);
            }
            if (bitch.GetType() == typeof(Healer))
            {
                movement = MoveHealer(bitch, bitches);
            }
            if (movement == null)
            {
                movement = Vector2.Zero;
            }
            return movement;
        }

        /// <summary>
        /// Entry point for shooting arrows.
        /// </summary>
        /// <param name="bitch">Reference to the Archer.</param>
        /// <param name="bitches">List of playing BouncingBitches.</param>
        /// <returns></returns>
        public Vector2 HandleArrow(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            return MoveArrow(bitch, bitches);
        }

        #endregion

        #region General

        /// <summary>
        /// First internal interface for moving Arrows.
        /// </summary>
        /// <param name="bitch"></param>
        /// <param name="bitches"></param>
        /// <returns></returns>
        private Vector2 MoveArrow(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            switch (GetHealthLevel(bitch))
            {
                case HEALTH_LOW:
                    return ArrowLow(bitch, bitches);
                case HEALTH_MEDIUM:
                    return ArrowMedium(bitch, bitches);
                case HEALTH_HIGH:
                    return ArrowHigh(bitch, bitches);
            }
            return Vector2.Zero;
        }

        /// <summary>
        /// First internal interface for moving Archers.
        /// </summary>
        /// <param name="bitch"></param>
        /// <param name="bitches"></param>
        /// <returns></returns>
        private Vector2 MoveArcher(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            switch (GetHealthLevel(bitch))
            {
                case HEALTH_LOW:
                    return ArcherLow(bitch, bitches);
                case HEALTH_MEDIUM:
                    return ArcherMedium(bitch, bitches);
                case HEALTH_HIGH:
                    return ArcherHigh(bitch, bitches);
            }
            return Vector2.Zero;
        }

        /// <summary>
        /// First internal interface for moving Gladiators.
        /// </summary>
        /// <param name="bitch"></param>
        /// <param name="bitches"></param>
        /// <returns></returns>
        private Vector2 MoveGladiator(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            switch (GetHealthLevel(bitch))
            {
                case HEALTH_LOW:
                    return GladiatorLow(bitch, bitches);
                case HEALTH_MEDIUM:
                    return GladiatorMedium(bitch, bitches);
                case HEALTH_HIGH:
                    return GladiatorHigh(bitch, bitches);
            }
            return Vector2.Zero;
        }

        /// <summary>
        /// First internal interface for moving Healers.
        /// </summary>
        /// <param name="bitch"></param>
        /// <param name="bitches"></param>
        /// <returns></returns>
        private Vector2 MoveHealer(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            switch (GetHealthLevel(bitch))
            {
                case HEALTH_LOW:
                    return HealerLow(bitch, bitches);
                case HEALTH_MEDIUM:
                    return HealerMedium(bitch, bitches);
                case HEALTH_HIGH:
                    return HealerHigh(bitch, bitches);
            }
            return Vector2.Zero;
        }

        /// <summary>
        /// Determine health level based on predefined values.
        /// </summary>
        /// <param name="bitch"></param>
        /// <returns></returns>
        private String GetHealthLevel(BouncingBitch bitch)
        {
            if (bitch.Health > HEALTH_LIMIT_SUPERIOR)
            {
                return HEALTH_HIGH;
            }
            else if (bitch.Health > HEALTH_LIMIT_INFERIOR)
            {
                return HEALTH_MEDIUM;
            }
            else
            {
                return HEALTH_LOW;
            }
        }

        #endregion

        #region Archer

        private Vector2 ArcherLow(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            Random random = new Random((int)DateTime.Now.Ticks);
            if (random.Next(0, 11) > 5)
            {
                return MoveTo(bitch, new Vector2(Game1.SCREEN_WIDTH - Game1.RIGHT_BOUND_OFFSET, 0));
            }
            else
            {
                return MoveTo(bitch, new Vector2(Game1.SCREEN_WIDTH - Game1.RIGHT_BOUND_OFFSET, Game1.SCREEN_HEIGHT));
            }
        }

        private Vector2 ArcherMedium(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            Random random = new Random((int)DateTime.Now.Ticks);
            if (random.Next(0, 11) > 5)
            {
                return MoveTo(bitch, new Vector2(Game1.SCREEN_WIDTH - Game1.RIGHT_BOUND_OFFSET, 0));
            }
            else
            {
                return MoveTo(bitch, new Vector2(Game1.SCREEN_WIDTH - Game1.RIGHT_BOUND_OFFSET, Game1.SCREEN_HEIGHT));
            }
        }

        private Vector2 ArcherHigh(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            Random random = new Random((int)DateTime.Now.Ticks);
            if (random.Next(0, 11) > 5)
            {
                return MoveTo(bitch, new Vector2(Game1.SCREEN_WIDTH - Game1.RIGHT_BOUND_OFFSET, 0));
            }
            else
            {
                return MoveTo(bitch, new Vector2(Game1.SCREEN_WIDTH - Game1.RIGHT_BOUND_OFFSET, Game1.SCREEN_HEIGHT));
            }
        }

        #endregion

        #region Arrow

        private Vector2 ArrowLow(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target = FindClosest(bitch, bitches, false, null);
            return Ranged(bitch, target);
        }

        private Vector2 ArrowMedium(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target = FindClosest(bitch, bitches, false, null);
            return Ranged(bitch, target);
        }

        private Vector2 ArrowHigh(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target = FindClosest(bitch, bitches, false, null);
            return Ranged(bitch, target);
        }

        #endregion

        #region Gladiator

        private Vector2 GladiatorLow(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target = FindClosest(bitch, bitches, false, null);
            return ChargeTo(bitch, target);
        }

        private Vector2 GladiatorMedium(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target = FindClosest(bitch, bitches, false, null);
            return ChargeTo(bitch, target);
        }

        private Vector2 GladiatorHigh(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target = FindClosest(bitch, bitches, false, null);
            return ChargeTo(bitch, target);
        }

        #endregion

        #region Healer

        private Vector2 HealerLow(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target;
            target = FindClosest(bitch, bitches, true, null);
            if (target == null)
            {
                target = FindClosest(bitch, bitches, false, null);
            }
            return ChargeTo(bitch, target);
        }

        private Vector2 HealerMedium(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target;
            target = FindClosest(bitch, bitches, true, null);
            if (target == null)
            {
                target = FindClosest(bitch, bitches, false, null);
            }
            return ChargeTo(bitch, target);
        }

        private Vector2 HealerHigh(BouncingBitch bitch, List<BouncingBitch> bitches)
        {
            BouncingBitch target;
            target = FindClosest(bitch, bitches, true, null);
            if (target == null)
            {
                target = FindClosest(bitch, bitches, false, null);
            }
            return ChargeTo(bitch, target);
        }

        #endregion

        #region Utils

        /// <summary>
        /// Finds the closest BouncingBitch according to specified characteristics.
        /// </summary>
        /// <param name="bitch">BouncingBitch used as reference.</param>
        /// <param name="bitches">List of all possible BouncingBitches.</param>
        /// <param name="fromSameTeam">True if it must be from the same team.</param>
        /// <param name="type">Type of BouncingBitch (Archer, Healer, Gladiator) - null if any type is fine.</param>        
        private BouncingBitch FindClosest(BouncingBitch bitch, List<BouncingBitch> bitches, bool fromSameTeam, Type type)
        {
            BouncingBitch closest = null;
            foreach (BouncingBitch b in bitches)
            {
                if (closest == null)
                {
                    if (b != bitch)
                    {
                        if ((fromSameTeam && (b.Player == bitch.Player)) || (!fromSameTeam && (b.Player != bitch.Player)))
                        {
                            if (type == null || ((type != null) && (b.GetType() == type)))
                            {
                                closest = b;
                            }
                        }
                    }
                }
                else
                {
                    if (b != bitch)
                    {
                        if ((fromSameTeam && (b.Player == bitch.Player)) || (!fromSameTeam && (b.Player != bitch.Player)))
                        {
                            if (type == null || ((type != null) && (b.GetType() == type)))
                            {
                                if (GetDistance(b, bitch) < GetDistance(closest, bitch))
                                {
                                    closest = b;
                                }
                            }
                        }
                    }
                }
            }
            return closest;
        }

        /// <summary>
        /// Calculate distance between two BouncingBitches.       
        /// </summary>
        /// <param name="a">One element.</param>
        /// <param name="b">The other.</param>        
        private double GetDistance(BouncingBitch a, BouncingBitch b)
        {
            return Math.Sqrt(Math.Pow(a.Position.X - b.Position.X, 2) + Math.Pow(a.Position.Y - b.Position.Y, 2));
        }

        /// <summary>
        /// Final implementation (or interface) to perform a ranged attack using an Archer.
        /// </summary>
        /// <param name="bitch">Attacker.</param>
        /// <param name="target">Target.</param>
        private Vector2 Ranged(BouncingBitch bitch, BouncingBitch target)
        {
            Vector2 arrowAttack = new Vector2(target.Position.X - bitch.Position.X + POSITION_CORRECTION, target.Position.Y - bitch.Position.Y + POSITION_CORRECTION);
            arrowAttack.Normalize();
            return arrowAttack;
        }

        /// <summary>
        /// Final implementation (or interface) to perform a melee attack.
        /// </summary>
        /// <param name="bitch">Attacker.</param>
        /// <param name="target">Target.</param>
        private Vector2 ChargeTo(BouncingBitch bitch, BouncingBitch target)
        {
            Vector2 movement;
            try
            {
                movement = new Vector2(target.Position.X - bitch.Position.X, target.Position.Y - bitch.Position.Y);
                movement.Normalize();
                movement *= DefinePowerState.MAXIMUM_SPEED;
                if (movement == null)
                {
                    movement = Vector2.Zero;
                }
            }
            catch (Exception)
            {
                movement = Vector2.Zero;
            }
            return movement;
        }

        /// <summary>
        /// Final implementation (or interface) to move a BouncingBitch.
        /// </summary>
        /// <param name="bitch">BouncingBitch to be moved.</param>
        /// <param name="position">Destination.</param>
        private Vector2 MoveTo(BouncingBitch bitch, Vector2 position)
        {
            float vox = (float)Math.Sqrt((double)Math.Abs((2.0f * BallRageState.DESACCELARATION_RATE * (position.X - bitch.Position.X))));
            vox *= CORRECTION_FACTOR * Math.Sign(position.X - bitch.Position.X);
            float voy = (float)Math.Sqrt((double)Math.Abs((2.0f * BallRageState.DESACCELARATION_RATE * (position.Y - bitch.Position.Y))));
            voy *= CORRECTION_FACTOR * Math.Sign(position.Y - bitch.Position.Y);
            return new Vector2(vox, voy);
        }

        #endregion
    }
}
